#include "ZPCIeDevice.hpp"
#include <asm-generic/errno.h>
#include <fcntl.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
#include <future>
#include <list>
#include "stdio.h"
#include "sys/ioctl.h"
#include "zviewctl.h"
#include "errno.h"
ZPCIeDevice::ZPCIeDevice (const DeviceInfo &info) : CZViewDevice (info)
{
    memset (&m_iZViewDevInfo, 0, sizeof (m_iZViewDevInfo));
    memset (&m_iIntTable, 0, sizeof (zview_device_int_table));
    memset (&m_iMemInfo, 0, sizeof (zview_device_memory_info));
    memset (&m_arrMemAddr, 0, sizeof (m_arrMemAddr));
}

ZPCIeDevice::~ZPCIeDevice ()
{
    Close ();
};

bool ZPCIeDevice::Open ()
{
    if (m_iInfo.strPath.empty ())
        return false;
    m_fd = open (m_iInfo.strPath.c_str (), O_RDWR);

    if (m_fd < 0)
    {
        fprintf (stderr, "file %s open fail\n", m_iInfo.strPath.c_str ());
        return false;
    }
    if (!InitPciInfo ())
    {
        Close ();
        return false;
    }

    return true;
};

bool ZPCIeDevice::IsOpen ()
{
    return m_fd != -1;
}

bool ZPCIeDevice::Close ()
{
    bool rst = true;

    ClearMapBar ();
    StopMontiorIntThd();   
    StopMontiorDmaThd();

    if (close (m_fd) < 0)
    {
        fprintf (stderr, "file %s close fail\n", m_iInfo.strPath.c_str ());
        rst = false;
    }
    m_fd = -1;
    return rst;
}

void readmem (struct zview_device_io *info, const void *mem, size_t len)
{
    void *ptr[] {
            &info->data8, &info->data16, &info->data32, nullptr, &info->data64,
    };
    memcpy (ptr[len / 2], mem, len);
}

void writemem (const zview_device_io *info, void *mem, size_t len)
{
    void const *ptr[] {
            &info->data8, &info->data16, &info->data32, nullptr, &info->data64,
    };
    memcpy (mem, ptr[len / 2], len);
}

bool ZPCIeDevice::Write (uint8_t bar, uint32_t offset, const void *data, uint32_t len)
{
    if (bar >= m_iMemInfo.memory_count)
    {
        fprintf (stderr, "pci write:device %s des bar %d must be smaller than %d \n", m_iInfo.strPath.c_str (), bar, m_iMemInfo.memory_count);
        return false;
    }
    if (!IsOpen ())
    {
        fprintf (stderr, "device %s must open before write\n", m_iInfo.strPath.c_str ());
        return false;
    }

    if (m_arrMemAddr[bar] != nullptr || (m_bMapUsed && MapMem ({bar})))
    {
        memcpy ((char *) m_arrMemAddr[bar] + offset, data, len);
        return true;
    }

    // len 拆分成若干 4字节 每次写4字节
    // 目前没有64bit写

    uint32_t n32BytesTime = len / sizeof (uint32_t);
    uint32_t nLastBytes   = len % sizeof (uint32_t);
    uint32_t nSendLen     = sizeof (uint32_t);

    zview_device_io io_info {};
    io_info.bar = bar;
    io_info.dir = ZVIEW_WRITE;

    io_info.type     = TYPE_UINT32;
    const void *pPos = data;

    for (uint32_t i = 0; i <= n32BytesTime; ++i)
    {
        pPos           = (const char *) data + i * sizeof (uint32_t);
        io_info.offset = offset + i * sizeof (uint32_t);
        if (i == n32BytesTime)
        {
            nSendLen     = nLastBytes;
            io_info.type = (zview_io_data_type) (nLastBytes / 2);
        }

        if (nSendLen == 0)
            return true;

        readmem (&io_info, pPos, nSendLen);
        if (ioctl (m_fd, IOC_ZVIEW_IO_WR, &io_info) < 0)
        {
            fprintf (stderr, "device %s write bar %d ,offset %lu error\n", m_iInfo.strPath.c_str (), bar, io_info.offset);
            return false;
        }
    }
    
    return true;
}

bool ZPCIeDevice::Read (uint8_t bar, uint32_t offset, void *data, uint32_t len)
{
    if (bar >= m_iMemInfo.memory_count)
    {
        fprintf (stderr, "pci write:device %s des bar %d must be smaller than %d \n", m_iInfo.strPath.c_str (), bar, m_iMemInfo.memory_count);
        return false;
    }
    if (!IsOpen ())
    {
        fprintf (stderr, "device %s must open before read\n", m_iInfo.strPath.c_str ());
        return false;
    }
    if (m_arrMemAddr[bar] != nullptr || (m_bMapUsed && MapMem ({bar})))
    {
        memcpy (data, (const char *) m_arrMemAddr[bar] + offset, len);
        return true;
    }

    // len 拆分成若干 4字节 每次写4字节
    // 目前没有64bit写

    uint32_t n32BytesTime = len / sizeof (uint32_t);
    uint32_t nLastBytes   = len % sizeof (uint32_t);
    uint32_t nSendLen     = sizeof (uint32_t);

    zview_device_io io_info {};
    io_info.bar = bar;
    io_info.dir = ZVIEW_READ;

    io_info.type = TYPE_UINT32;
    char *pPos   = (char *) data;

    for (uint32_t i = 0; i <= n32BytesTime; ++i)
    {
        pPos           = (char *) data + i * sizeof (uint32_t);
        io_info.offset = offset + i * sizeof (uint32_t);
        if (i == n32BytesTime)
        {
            nSendLen     = nLastBytes;
            io_info.type = (zview_io_data_type) (nLastBytes / 2);
        }

        if (nSendLen == 0)
            return true;

        if (ioctl (m_fd, IOC_ZVIEW_IO_WR, &io_info) < 0)
        {
            fprintf (stderr, "device %s read bar %d ,offset %lu error\n", m_iInfo.strPath.c_str (), bar, io_info.offset);
            return false;
        }
        writemem (&io_info, pPos, nSendLen);
    }
    return true;
}


bool ZPCIeDevice::DmaC2H (void *dst, void *src, size_t size)
{
    zview_device_dma_transaction dma_transation;
    dma_transation.dma_type = zview_dma_type::SLAVE_TO_HOST;
    dma_transation.src = src;
    dma_transation.dst = dst;
    dma_transation.length = size; 
    
    int ret = 0;
    if((ret = ioctl (m_fd, IOC_ZVIEW_DMA_TRANSACTION, &dma_transation )) < 0)
    {
        fprintf(stderr,"device %s dma c2h fail %d\n",m_iInfo.strPath.c_str(),ret);
        return false;
    }

    return true;
}

bool ZPCIeDevice::DmaH2CAsyn (void *dst, void *src,uint32_t size, std::function<void (uint64_t)> callback)
{
    zview_device_dma_transaction dma_transation;
    dma_transation.dma_type = zview_dma_type::HOST_TO_SLAVE;
    dma_transation.src = src;
    dma_transation.dst = dst;
    dma_transation.length = size; 


    int fd =  eventfd(0,EFD_CLOEXEC|EFD_SEMAPHORE|EFD_NONBLOCK);
    if(fd<=0)
    {
       fprintf(stderr,"device %s dma h2c create eventfd fail\n",m_iInfo.strPath.c_str());
    }

    dma_transation.notify_event_fd = fd;

    int ret = 0;
    if((ret = ioctl (m_fd, IOC_ZVIEW_DMA_TRANSACTION, &dma_transation)) < 0)
    {
        fprintf(stderr,"device %s dma h2c asyn fail %d\n",m_iInfo.strPath.c_str(),ret);
        return false;
    }

    InsertDmaTransaction(dma_transation,callback);
    return true;
}

bool ZPCIeDevice::DmaC2HAsyn (void *dst, void *src,uint32_t size, std::function<void (uint64_t)> callback)
{
    zview_device_dma_transaction dma_transation;
    dma_transation.dma_type = zview_dma_type::SLAVE_TO_HOST;
    dma_transation.src = src;
    dma_transation.dst = dst;
    dma_transation.length = size; 
    int fd =  eventfd(0,EFD_CLOEXEC|EFD_SEMAPHORE|EFD_NONBLOCK);
    if(fd<=0)
    {
       fprintf(stderr,"device %s dma c2h create eventfd fail\n",m_iInfo.strPath.c_str());
    }

    dma_transation.notify_event_fd = fd;

    int ret = 0;
    if((ret = ioctl (m_fd, IOC_ZVIEW_DMA_TRANSACTION, &dma_transation)) < 0)
    {
        fprintf(stderr,"device %s dma c2h asyn fail %d\n",m_iInfo.strPath.c_str(),errno);
        return false;
    }

    InsertDmaTransaction(dma_transation,callback);
    return true;
}

bool ZPCIeDevice::DmaH2C (void *dst, void *src, size_t size)
{
    zview_device_dma_transaction dma_transation;
    dma_transation.dma_type = zview_dma_type::HOST_TO_SLAVE;
    dma_transation.src = src;
    dma_transation.dst = dst;
    dma_transation.length = size; 
    int ret = 0;
    if((ret = ioctl (m_fd, IOC_ZVIEW_DMA_TRANSACTION, &dma_transation )) < 0)
    {
        fprintf(stderr,"device %s dma h2c fail, %d\n",m_iInfo.strPath.c_str(),errno);
        return false;
    }
    return true;   
}




bool ZPCIeDevice::MapMem (const std::initializer_list<uint8_t> &list)
{
    if (!m_bMapUsed)
    {
        fprintf (stderr, "mapmem: device %s must allow map first\n", m_iInfo.strPath.c_str ());
        return false;
    }
    for (auto &item: list)
    {
        if (!MapBar (item))
            return false;
    }
    return true;
}

void *ZPCIeDevice::GetMapMemAddr (uint8_t bar)
{
    if (bar >= PCI_MAX_BAR_COUNT)
        return nullptr;
    if (m_arrMemAddr[bar] != nullptr)
        return m_arrMemAddr[bar];
    return nullptr;
}

void ZPCIeDevice::UseMap (bool used)
{
    m_bMapUsed = used;
    if (!m_bMapUsed)
    {
        ClearMapBar ();
    }
};

int ZPCIeDevice::WaitInt (zview_interrupt_id interrupt_code, uint32_t timeout)
{
    int fd =  eventfd(0,EFD_CLOEXEC|EFD_SEMAPHORE|EFD_NONBLOCK);

    m_mutexInterrupt.lock();
    auto iter = m_mapInterruptEventFds.insert({interrupt_code,fd});
    m_mutexInterrupt.unlock();

    fd_set sets;
    FD_ZERO (&sets);
    FD_SET (fd, &sets);
    timeval tm {};
    tm.tv_sec  = timeout / 1000;
    tm.tv_usec = timeout % 1000 * 1000;
    int ret    = select (fd + 1, &sets, nullptr, nullptr, &tm);
    
    close(fd);
    m_mutexInterrupt.lock();
    m_mapInterruptEventFds.erase(iter);
    m_mutexInterrupt.unlock();

    if(ret <= 0)
        fprintf (stderr, "device %s wait int %d rst: %d\n", m_iInfo.strPath.c_str (),interrupt_code, errno);
    return ret<=0? -1 :0; 
}

bool ZPCIeDevice::GetInrTable (zview_device_int_table &int_table)
{
    if (ioctl (m_fd, IOC_ZVIEW_GET_INT_INFO_TABLE, &int_table) < 0)
    {
        fprintf (stderr, "device %s get init table error\n", m_iInfo.strPath.c_str ());
        return false;
    }
    return true;
}

int ZPCIeDevice::GetDeviceFd () const
{
    return m_fd;
}

bool ZPCIeDevice::MonitorInt ()
{
    fd_set  sets;
    timeval tm {};
    tm.tv_sec  = 0;
    tm.tv_usec = 5 * 1000;  // 5ms循环一次

    while (IsOpen () && m_bMonitorInt.load ())
    {
        FD_ZERO (&sets);
        FD_SET (m_fd, &sets);
        int ret = select (m_fd + 1, &sets, nullptr, nullptr, &tm);
        if( ret > 0 )
        {
            uint64_t mask = GetInrMask();
            if(mask == 0 )
                continue;   //无中断产生
            //有中断产生
            std::unique_lock<std::recursive_mutex> lk{m_mutexInterrupt};
            uint64_t check_mask = 1;
            for(uint8_t i = 0 ;i< sizeof(uint64_t)*8;++i)
            {
                if(mask & (check_mask<<i))
                {
                    auto fds = m_mapInterruptEventFds.equal_range((zview_interrupt_id)i);
                    auto iter = fds.first;
                    for(;iter!=fds.second;++iter)
                    {
                        eventfd_write(iter->second,1);      //通知可读
                    }
                }
            }
        }
    }
    return true;
}

void ZPCIeDevice::StartMontiorIntThd () 
{
    m_bMonitorInt.store(true);
    m_thdInt = std::thread(&ZPCIeDevice::MonitorInt,this);
}

void ZPCIeDevice::StopMontiorIntThd () 
{
    m_bMonitorInt.store(false);
    if(m_thdInt.joinable())
        m_thdInt.join();
}

bool ZPCIeDevice::MonitorDmaAsyn ()
{
    fd_set  sets;
    timeval tm {};
    tm.tv_sec  = 0;
    tm.tv_usec = 5 * 1000;  // 5ms循环一次


    std::vector<int>  release_fds;
    while (IsOpen () && m_bMonitorDma.load ())
    {
        if (m_listDmaEventFds.empty())
        {
            select (0, nullptr, nullptr, nullptr, &tm);
            continue;
        }
        
        FD_ZERO (&sets);

        int fd_max = m_listDmaEventFds.begin()->first.notify_event_fd;
        for(auto& dma:m_listDmaEventFds)
        {
            FD_SET (dma.first.notify_event_fd, &sets);
            if(dma.first.notify_event_fd>fd_max)
                fd_max = dma.first.notify_event_fd;
        }

        int ret = select (fd_max + 1, &sets, nullptr, nullptr, &tm);
        if( ret > 0 )
        {
            std::unique_lock<std::recursive_mutex> lk(m_mutexDma);
            for(auto iter= m_listDmaEventFds.begin();iter!=end(m_listDmaEventFds);++iter)
            {
                auto& dma = *iter;
                if(FD_ISSET(dma.first.notify_event_fd,&sets))
                {
                    //获取执行结果
                    eventfd_t rst;
                    eventfd_read(dma.first.notify_event_fd,&rst);
                    //执行回调
                    dma.second.operator()(rst);

                    //清理事件句柄
                    close(dma.first.notify_event_fd);
                    iter = m_listDmaEventFds.erase(iter);
                }
            }
        }
    }

    //退出监控,清理事件
    std::unique_lock<std::recursive_mutex> lk(m_mutexDma);
    for(auto iter = begin(m_listDmaEventFds);iter!=end(m_listDmaEventFds);++iter)
    {
        close(iter->first.notify_event_fd);
        iter = m_listDmaEventFds.erase(iter);
    }
    return true;
}

void ZPCIeDevice::StartMonitorDmaThd () 
{
    if(m_bMonitorDma.load())
        return;
    m_bMonitorDma.store(true);
    m_thdDma = std::thread(&ZPCIeDevice::MonitorDmaAsyn,this);
}
void ZPCIeDevice::StopMontiorDmaThd () 
{
    m_bMonitorDma.store(false);
    if(m_thdDma.joinable())
        m_thdDma.join();
}
void ZPCIeDevice::InsertDmaTransaction (zview_device_dma_transaction trans,std::function<void(uint64_t)> callback) 
{
    std::unique_lock<std::recursive_mutex> lk(m_mutexDma);
    m_listDmaEventFds.emplace_back(std::make_pair(trans,callback));
};

uint64_t ZPCIeDevice::GetInrMask () const
{
    if (m_fd < 0)
        return 0;
    uint64_t mask;
    if (read (m_fd, &mask, sizeof (mask)) < 0)
    {
        fprintf (stderr, "device %s get inr mask error\n", m_iInfo.strPath.c_str ());
    }
    return mask;
};

bool ZPCIeDevice::MapBar (uint8_t bar)
{
    if (m_arrMemAddr[bar] != nullptr)
    {
        fprintf (stderr, "device %s bar %d had been mmap\n", m_iInfo.strPath.c_str (), bar);
        return false;
    }
    void *pMap = mmap (NULL, m_iMemInfo.memory_size[bar], PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, bar * getpagesize ());
    if (pMap == (void *) ~(intptr_t) 0)
        return false;
    m_arrMemAddr[bar] = pMap;
    return true;
}

bool ZPCIeDevice::UnmapBar (uint8_t bar)
{
    if (m_arrMemAddr[bar] == nullptr)
    {
        return true;
    }
    munmap (m_arrMemAddr[bar], m_iMemInfo.memory_size[bar]);
    m_arrMemAddr[bar] = nullptr;
    return true;
}

uint8_t ZPCIeDevice::GetBarCount ()
{
    return m_iMemInfo.memory_count;
}

bool ZPCIeDevice::InitPciInfo ()
{
    int ret = ioctl (m_fd, IOC_ZVIEW_GET_DEV_INFO, &m_iZViewDevInfo);
    if (ret < 0)
    {
        fprintf (stderr, "get pci %s info fail\n", m_iInfo.strName.c_str ());
        return false;
    }
    if ((ret = ioctl (m_fd, IOC_ZVIEW_GET_PHY_MEM_INFO, &m_iMemInfo)) < 0)
    {
        fprintf (stderr, "get pci %s mem info fail\n", m_iInfo.strName.c_str ());
        return false;
    }

    if (GetInrTable (m_iIntTable) && m_iIntTable.interrupt_count > 0)
    {
        StartMontiorIntThd();
    }

    StartMonitorDmaThd();

    return true;
}


void ZPCIeDevice::ClearMapBar ()
{
    for (size_t i = 0; i < m_iMemInfo.memory_count; ++i)
    {
        if (m_arrMemAddr[i] == nullptr)
            continue;
        UnmapBar (i);
    }
}